package com.gitee.linmt.config;

import com.gitee.linmt.base.BaseDictionaryEnum;
import com.gitee.linmt.properties.DictionaryEnumProperties;
import com.gitee.linmt.register.DictionaryEnumTypeHandlerRegister;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.logging.log4j.util.Strings;
import org.mybatis.logging.Logger;
import org.mybatis.logging.LoggerFactory;
import org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

/**
 * @author lin-mt
 * @version 1.0.0
 * @since 1.0.0
 */
@Configuration
@EnableConfigurationProperties(DictionaryEnumProperties.class)
@ConditionalOnSingleCandidate(SqlSessionFactory.class)
@AutoConfigureAfter(MybatisAutoConfiguration.class)
public class DictionaryEnumAutoConfig<E extends BaseDictionaryEnum> {

    public static final String MYBATIS_DICTIONARY_ENUM_PREFIX = "dictionary-enum.mybatis";

    private static final Logger LOGGER = LoggerFactory.getLogger(DictionaryEnumAutoConfig.class);
    private static final ResourcePatternResolver RESOURCE_PATTERN_RESOLVER = new PathMatchingResourcePatternResolver();
    private static final MetadataReaderFactory METADATA_READER_FACTORY = new CachingMetadataReaderFactory();

    private final DictionaryEnumProperties dictionaryEnumProperties;

    public DictionaryEnumAutoConfig(DictionaryEnumProperties dictionaryEnumProperties) {
        this.dictionaryEnumProperties = dictionaryEnumProperties;
    }

    @Bean
    public DictionaryEnumTypeHandlerRegister<E> registerDictionaryEnumTypeHandler() throws IOException {
        String enumPackages = dictionaryEnumProperties.getEnumPackages();
        Set<Class<E>> dictionaryEnums = new HashSet<>();
        if (Strings.isNotBlank(enumPackages)) {
            dictionaryEnums.addAll(scanClasses(enumPackages));
        }
        Class<?> baseClass = dictionaryEnumProperties.getBaseEnumClass();
        if (baseClass != null) {
            String packageName = ClassUtils.getPackageName(baseClass);
            dictionaryEnums.addAll(scanClasses(packageName));
        }
        return new DictionaryEnumTypeHandlerRegister<>(dictionaryEnums, this.dictionaryEnumProperties);
    }

    private Set<Class<E>> scanClasses(String packagePatterns) throws IOException {
        Set<Class<E>> classes = new HashSet<>();
        String[] packagePatternArray = StringUtils.tokenizeToStringArray(packagePatterns,
                ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
        for (String packagePattern : packagePatternArray) {
            Resource[] resources = RESOURCE_PATTERN_RESOLVER.getResources(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
                    + ClassUtils.convertClassNameToResourcePath(packagePattern) + "/**/*.class");
            for (Resource resource : resources) {
                try {
                    ClassMetadata classMetadata = METADATA_READER_FACTORY.getMetadataReader(resource).getClassMetadata();
                    Class<?> clazz = Resources.classForName(classMetadata.getClassName());
                    if (BaseDictionaryEnum.class.isAssignableFrom(clazz)) {
                        //noinspection unchecked
                        classes.add((Class<E>) clazz);
                    }
                } catch (Throwable e) {
                    LOGGER.warn(() -> "Cannot load the '" + resource + "'. Cause by " + e.toString());
                }
            }
        }
        return classes;
    }
}
